package auth

import (
	"context"
	"crypto/subtle"
	"errors"
	"go-caipu/pkg/infra/log"
	"go-caipu/pkg/services/admin/sysuser"
	"go-caipu/pkg/services/auth/authattempt"
	"go-caipu/pkg/setting"
	"go-caipu/pkg/util"
)

var (
	ErrPasswordEmpty        = errors.New("no password provided")
	ErrInvalidCredentials   = errors.New("invalid username or password")
	ErrTooManyLoginAttempts = errors.New("too many consecutive incorrect auth attempts for sysuser - auth for sysuser temporarily blocked")
	ErrUserDisabled         = errors.New("sysuser is disabled")
)
var loginLogger = log.New("auth")

const PREFIX = "admin"

type Authenticator interface {
	AuthenticateUser(ctx context.Context, query *UserLoginCommand) (*sysuser.User, error)
	AuthCheckRole(ctx context.Context, userId int64, method string, path string) (bool, error)
}
type AuthenticatorService struct {
	userService        sysuser.Service
	authAttemptService authattempt.Service
	cfg                *setting.Cfg
}

func ProvideService(user sysuser.Service, loginAttempt authattempt.Service, cfg *setting.Cfg) *AuthenticatorService {
	return &AuthenticatorService{
		userService:        user,
		authAttemptService: loginAttempt,
		cfg:                cfg,
	}
}

func (l *AuthenticatorService) AuthenticateUser(ctx context.Context, query *UserLoginCommand) (*sysuser.User, error) {
	ok, err := l.authAttemptService.Validate(ctx, PREFIX+query.Username)
	if err != nil {
		return nil, err
	}
	// 超过次数锁定
	if !ok {
		return nil, ErrTooManyLoginAttempts
	}
	if err := validatePasswordSet(query.Password); err != nil {
		return nil, err
	}
	user, err2 := l.userService.GetUserByUserName(ctx, query.Username)

	if err2 != nil {
		return nil, err2
	}

	if user.Status == 0 {
		return nil, ErrUserDisabled
	}
	if err := validatePassword(query.Password, user.Password, user.Salt); err != nil {
		return nil, err
	}

	return &user, nil
}
func validatePasswordSet(password string) error {
	if len(password) == 0 {
		return ErrPasswordEmpty
	}

	return nil
}

var validatePassword = func(providedPassword string, userPassword string, userSalt string) error {
	passwordHashed, err := util.EncodePassword(providedPassword, userSalt)
	if err != nil {
		return err
	}
	if subtle.ConstantTimeCompare([]byte(passwordHashed), []byte(userPassword)) != 1 {
		return ErrInvalidCredentials
	}

	return nil
}

func (l *AuthenticatorService) AuthCheckRole(ctx context.Context, userId int64, method string, path string) (bool, error) {
	return l.userService.AuthCheckRole(ctx, userId, method, path)
}
